import _ from 'lodash'

function isDef(val) {
    return val !== undefined && val !== null
}

function getFirstComponentChild(children) {
    if (Array.isArray(children)) {
        for (let i = 0; i < children.length; i++) {
            const c = children[i];
            if (isDef(c) && isDef(c.componentOptions)) {
                return c
            }
        }
    }
}

function getComponentName(opts) {
    return opts && (opts.Ctor.options.name || opts.tag)
}

function matches(pattern, name) {
    if (Array.isArray(pattern)) {
        return pattern.indexOf(name) > -1
    } else if (typeof pattern === 'string') {
        return pattern.split(',').indexOf(name) > -1
    } else if (_.isRegExp(pattern)) {
        return pattern.test(name)
    }
    /* istanbul ignore next */
    return false
}

function pruneCache(cache, current, filter) {
    for (const key in cache) {
        const cachedNode = cache[key];
        if (cachedNode) {
            const name = getComponentName(cachedNode.componentOptions);
            if (name && !filter(name)) {
                if (cachedNode !== current) {
                    pruneCacheEntry(cachedNode)
                }
                cache[key] = null
            }
        }
    }
}

function pruneCacheEntry(vnode) {
    if (vnode) {
        vnode.componentInstance.$destroy()
    }
}

export default {
    name: 'lease-keep-alive',
    props: {
        include: [],
        exclude: [],
        updateComponentsKey: Function
    },

    created() {
        this.cache = Object.create(null)
    },

    destroyed() {
        for (const key in this.cache) {
            pruneCacheEntry(this.cache[key])
        }
    },

    watch: {
        include(val) {
            pruneCache(this.cache, this._vnode, name => matches(val, name))
        },
        exclude(val) {
            pruneCache(this.cache, this._vnode, name => !matches(val, name))
        }
    },

    render() {
        const vnode = getFirstComponentChild(this.$slots.default);
        const componentOptions = vnode && vnode.componentOptions;
        if (componentOptions) {
            // check pattern
            const name = getComponentName(componentOptions);
            if (name && (
                (this.include && !matches(this.include, name)) ||
                (this.exclude && matches(this.exclude, name))
            )) {
                return vnode
            }
            const key = vnode.key == null
                // same constructor may get registered as different local components
                // so cid alone is not enough (#3269)
                ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
                : vnode.key;
            this.updateComponentsKey && this.updateComponentsKey(key);
            if (this.cache[key]) {
                vnode.componentInstance = this.cache[key].componentInstance
            } else {
                this.cache[key] = vnode
            }
            vnode.data.keepAlive = true
        }
        return vnode
    },
    methods: {
        removeCacheByKey(key) {
            pruneCacheEntry(this.cache[key]);
            this.cache[key] = null;
            delete this.cache[key];
        },
        removeAllCache() {
            for (const key in this.cache) {
                pruneCacheEntry(this.cache[key])
                this.cache[key] = null;
                delete this.cache[key];
            }
        },
        removeOtherCache(keepKey) {
            for (const key in this.cache) {
                if (key !== keepKey) {
                    pruneCacheEntry(this.cache[key])
                    this.cache[key] = null;
                    delete this.cache[key];
                }
            }
        }
    }
}
